#pragma once

#include <iostream>
#include <cstdio>
#include <vector>
#include <queue>
#include <unistd.h>
#include <pthread.h>

#include "LocalGuard.hpp"
#include "LogMessage.hpp"
#include "Pthread.hpp"

template <class T>
class ThreadPool
{
private:
    ThreadPool(size_t thread_num = 5, size_t task_num = 10)
        : _thread_num(thread_num), _task_num(task_num)
    {
        pthread_mutex_init(&_mutex, nullptr);
        pthread_cond_init(&_cond, nullptr);

        for (int i = 0; i < _thread_num; i++)
        {
            std::string thread_name = GetPthreadName();
            _threads.push_back(Thread<void, std::string>(std::bind(&ThreadPool::ThreadRun, this, std::placeholders::_1), thread_name, thread_name));
            log.LogMessage(Debug, "create %s success", thread_name.c_str());
        }
    }
    ThreadPool(const ThreadPool<T> &tp) = delete;
    const ThreadPool<T> &operator=(const ThreadPool<T> &tp) = delete;

public:
    static ThreadPool *GetInstance()
    {
        if (_instance == nullptr)
        {
            LocalGuard lg(&_mtinst);
            if (_instance == nullptr)
            {
                _instance = new ThreadPool<T>();
            }
        }

        return _instance;
    }

    ~ThreadPool()
    {
        pthread_mutex_destroy(&_mutex);
        pthread_cond_destroy(&_cond);
    }

    void ThreadRun(std::string name)
    {
        T date;
        while (true)
        {
            {
                LocalGuard lg(&_mutex);
                while (_task_queue.empty())
                {
                    ThreadWait(name);
                }

                date = _task_queue.front();
                _task_queue.pop();
                log.LogMessage(Debug, "%s, get a tast", name.c_str());
            }
            date.Run();
            date.PrintRest();
        }
    }

    void Start()
    {
        for (auto &th : _threads)
        {
            th.Create();
        }
    }

    void Wait()
    {
        for (auto &th : _threads)
        {
            th.Jion();
        }
    }

    void ThreadWait(const std::string &thread_name)
    {
        log.LogMessage(Debug, "%s start to wait", thread_name.c_str());
        pthread_cond_wait(&_cond, &_mutex);
    }

    void ThreadWakeUp()
    {
        pthread_cond_signal(&_cond);
    }

    void Push(T date) // TODO
    {
        _task_queue.push(date);
        date.PrintQues();
        ThreadWakeUp();
    }

private:
    std::vector<Thread<void, std::string>> _threads;
    std::queue<T> _task_queue;
    size_t _thread_num;
    size_t _task_num;

    pthread_mutex_t _mutex;
    pthread_cond_t _cond;

    static ThreadPool<T> *_instance;
    static pthread_mutex_t _mtinst;

    Log log;
};

template <class T>
ThreadPool<T> *ThreadPool<T>::_instance = nullptr;

template <class T>
pthread_mutex_t ThreadPool<T>::_mtinst = PTHREAD_MUTEX_INITIALIZER;